﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Data.EntityClient;
using System.Xml.Linq;
using System.Reflection;
using System.IO;
using System.Data.Objects;

namespace Cherry.Agile.Data.Object.Metadata
{
    /// <summary>
    /// Accesor to metadata information 
    /// </summary>
    internal static class MetadataAccessor
    {
        #region Members

        static Dictionary<string, List<EntityMapping>> _cache = new Dictionary<string,List<EntityMapping>>();

        #endregion

        #region Private Methods

        private static XElement GetXmlMslFragment(Assembly assembly, string mslSpec)
        {
            string[] resourcePaths = mslSpec.Split('/');

            if (resourcePaths[0] != "*")
            {
                string assemblyName = resourcePaths[0];
                assembly = Assembly.Load(assemblyName);
                StringBuilder sb = new StringBuilder();

                for (int i = 1; i < resourcePaths.Length; i++)
                    sb.Append(resourcePaths[i]).Append(".");

                sb.Length = sb.Length - 1; // remove last dot

                Stream resourceStream = assembly.GetManifestResourceStream(sb.ToString());
                return XElement.Load(GetStringMslFragment(resourceStream), LoadOptions.None);
            }
            else
            {
                Stream resourceStream = assembly.GetManifestResourceStream(resourcePaths[1]);
                return XElement.Load(GetStringMslFragment(resourceStream), LoadOptions.None);
            }
        }
        private static StreamReader GetStringMslFragment(Stream resourceStream)
        {
            StreamReader reader = new StreamReader(resourceStream);
            return reader;
        }
        private static List<EntityMapping> CreateEntityMappings(XElement entityContainerMappingElement, string @namespace)
        {
            return  (from e in entityContainerMappingElement.Descendants()
                              where e.Name == XName.Get("EntitySetMapping", @namespace)
                              select EntityMapping.CreateEntityMapping(e, e.GetDefaultNamespace())).ToList();
        }
        #endregion

        #region Public Static Methods

        public static void PopulateMetadata<T>(this ObjectContext context)
        {
            if (_cache.ContainsKey(context.DefaultContainerName))
                return;
            var container = context.DefaultContainerName;
            var constr = context.Connection.ConnectionString.Split('=');
            var val = System.Configuration.ConfigurationManager.ConnectionStrings[constr[1]].ConnectionString;
            PopulateMetadata<T>(container, val);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="container"></param>
        /// <param name="connectionString"></param>
        public static void PopulateMetadata<T>(string container,string connectionString)
        {
            List<EntityMapping> entityMappings;

            if (!_cache.TryGetValue(container, out entityMappings))
            {
                lock (container)
                {
                    if (_cache.ContainsKey(container))
                        return;
                    EntityConnectionStringBuilder efConnectionStringBuilder = new EntityConnectionStringBuilder(connectionString);
                    var assembly = typeof(T).Assembly;

                    string mslSpec = (from s in efConnectionStringBuilder.Metadata.Split('|')
                                      where s.Contains(".msl")
                                      select s).Single();

                    XElement entityContainerMappingElement = GetXmlMslFragment( assembly, mslSpec.Replace("res://", ""));
                    XNamespace entityContainerMappingNamespace = entityContainerMappingElement.GetDefaultNamespace();

                    entityMappings = CreateEntityMappings(entityContainerMappingElement, entityContainerMappingNamespace.NamespaceName);

                    _cache.Add(container, entityMappings);
                }
            }

        }

        public static EntityMapping GetTableNameByEdmType<T>(this ObjectContext context)  
        {
            PopulateMetadata<T>(context);
            return GetTableNameByEdmType<T>(context.DefaultContainerName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="containerName"></param>
        /// <returns></returns>
        public static EntityMapping GetTableNameByEdmType<T>(string containerName) 
        {
            var name = typeof(T).Name;
            return (from e in _cache[containerName]
                    where e.EntityName == name
                    select e).FirstOrDefault();
        }
        #endregion

        internal static string GetColumnNameByEdmProperty<T1>(string containerName, string columnName)
        {
            var t = GetTableNameByEdmType<T1>(containerName);
            return t[columnName];
        }
    }
}
